

#include "orangepch.h"
#include "OpenGLShader.h"
#include "Core.h"
#include "Orange/Log.h"
#include "glm/glm.hpp"
#include <fstream>
#include "glm/gtc/type_ptr.hpp"
namespace Orange
{
	static GLenum ShaderTypeFromGLenum(std::string &type)
	{
		if (type == "vertex")
			return GL_VERTEX_SHADER;
		if (type == "fragment" || type == "pixel")
			return GL_FRAGMENT_SHADER;

		ORANGE_CORE_ERROR("Unknow Shader Type");
		return 0;
	}

	OpenGLShader::OpenGLShader(const std::string &filepath)
	{
		const std::string source = ReadFile(filepath);
		std::unordered_map<GLenum, std::string> shaderSources = PreProcess(source);
		Compile(shaderSources);

		size_t lastSlash = filepath.find_last_of("/\\");
		lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;
		size_t lastDot = filepath.rfind(".");
		size_t count = lastDot == std::string::npos ? filepath.size() - lastSlash : lastDot - lastSlash;

		m_Name = filepath.substr(lastSlash, count);
	};

	void OpenGLShader::SetUniformMat4(const std::string &name, const glm::mat4 &mat)
	{
		UploadUniformMat4(name, mat);
	};

	uint32_t OpenGLShader::GetUniformLocation(const std::string &name)
	{
		return glGetUniformLocation(m_RendererId, name.c_str());
	};
	void OpenGLShader::UploadUniformMat4(const std::string &name, const glm::mat4 &mat)
	{
		uint32_t location = GetUniformLocation(name);
		glUniformMatrix4fv(location,1,GL_FALSE,glm::value_ptr(mat));
	};

	OpenGLShader::OpenGLShader(const std::string &name, const std::string &vertexShaderSrc, const std::string &fragmentShaderSrc) : m_Name(name)
	{

		std::unordered_map<GLenum, std::string> shaderSources;
		shaderSources[GL_VERTEX_SHADER] = vertexShaderSrc;
		shaderSources[GL_FRAGMENT_SHADER] = fragmentShaderSrc;
		Compile(shaderSources);
	}
	OpenGLShader::~OpenGLShader()
	{
		glDeleteProgram(m_RendererId);
	};

	void OpenGLShader::Bind()
	{
		glUseProgram(m_RendererId);
	};
	void OpenGLShader::UnBind()
	{
		glUseProgram(0);
	};

	std::string OpenGLShader::ReadFile(const std::string &filepath)
	{
		std::string result;
		std::ifstream in(filepath, std::ios::in | std::ios::binary);

		if (in)
		{

			in.seekg(0, std::ios::end);
			result.resize(in.tellg());
			in.seekg(0, std::ios::beg);
			in.read(&result[0], result.size());

			in.close();
		}
		else
		{
			ORANGE_CORE_ASSERT(false, " could not open the file! ");
		}
		return result;
	}

	std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string &source)
	{
		std::unordered_map<GLenum, std::string> shaderSources;
		const char *typeToken = "#type";
		size_t tokenLen = strlen(typeToken);
		size_t pos = source.find(typeToken, 0);
		while (pos != std::string::npos)
		{
			size_t eol = source.find_first_of("\r\n", pos);
			ORANGE_CORE_ASSERT(eol != std::string::npos, "Syntax Error!");
			size_t begin = pos + tokenLen + 1;
			std::string type = source.substr(begin, eol - begin);

			ORANGE_CORE_ASSERT(ShaderTypeFromGLenum(type), "Invalid shader type");

			size_t nextLinePos = source.find_first_of("\r\n", eol);
			pos = source.find(typeToken, nextLinePos);
			shaderSources[ShaderTypeFromGLenum(type)] = source.substr(nextLinePos, pos -
																					   (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));
		}
		return shaderSources;
	}

	void OpenGLShader::Compile(std::unordered_map<GLenum, std::string> &shaderSource)
	{

		GLenum program = glCreateProgram();
		std::array<GLenum, 2> shaderIds;
		unsigned int shaderIdsIndex = 0;
		for (auto &kv : shaderSource)
		{

			GLenum type = kv.first;
			std::string shaderSrc = kv.second;
			// Create an empty vertex shader handle
			GLenum shader = glCreateShader(type);

			// Send the vertex shader source code to GL
			// Note that std::string's .c_str is NULL character terminated.
			const GLchar *source = shaderSrc.c_str();
			glShaderSource(shader, 1, &source, 0);

			// Compile the vertex shader
			glCompileShader(shader);

			GLint isCompiled = 0;
			glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
			if (isCompiled == GL_FALSE)
			{
				GLint maxLength = 0;
				glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);

				std::vector<GLchar> infoLog(maxLength);
				glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);

				glDeleteShader(shader);

				ORANGE_CORE_ERROR(infoLog.data());
				ORANGE_CORE_ASSERT(false, " shader compiled failure!");

				break;
			}
			glAttachShader(program, shader);
			shaderIds[shaderIdsIndex++] = (shader);
		}

		m_RendererId = program;

		glLinkProgram(program);

		GLint isLinked = 0;
		glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);
		if (isLinked == GL_FALSE)
		{
			GLint maxLength = 0;
			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);

			// The maxLength includes the NULL character
			std::vector<GLchar> infoLog(maxLength);
			glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);

			// We don't need the program anymore.
			glDeleteProgram(program);
			// Don't leak shaders either.
			for (auto id : shaderIds)
			{
				glDeleteShader(id);
			}

			// Use the infoLog as you see fit.

			ORANGE_CORE_ERROR(infoLog.data());
			ORANGE_CORE_ASSERT(false, "link shader program failure!");

			// In this simple program, we'll just leave
			return;
		}
		for (auto id : shaderIds)
		{
			glDetachShader(m_RendererId, id);
		}
	}
};
